home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Best of www.BestZips.com (Collector's Edition)
/
Best of WWW.BESTZIPS.COM Collector's Edition (JCSM Shareware) (JCS Marketing).ISO
/
editors_
/
ze16v250.zip
/
CTAGS.C_
/
CTAGS.C
Wrap
C/C++ Source or Header
|
1996-01-18
|
23KB
|
910 lines
/***************************************************************************
General Information and Notes Regarding the CTAGS.C File
========================================================
This file has been provided as an example of how to write a MS-DOS utility
that can interface to the Zeus Tool Manager interface. Some very minor
changes have been made to make this code work with Zeus. All changes have
been marked using the initalis JAJ. This file is one of the files that make
up the Elvis editor which is a VI clone (for more information see the Elvis
editor refer to the README.TXT which is also shown below).
To compile this file refer to the CTAGS.MAK make or CTAGS.IDE project file
supplied in the EXAMPLES directory. To run the the program you can use
the CTAGS.EXE from the command line of just open an valid C or C++ file and
select the Zeus Tools CTAGS Utility menu item.
---------------- JAJ - Copy of the original Elvis README.TXT ------------
Elvis is a clone of vi/ex, the standard UNIX editor. Elvis supports
nearly all of the vi/ex commands, in both visual mode and colon mode.
Elvis runs under BSD UNIX, AT&T SysV UNIX, SCO Xenix, Minix, MS-DOS
(Turbo-C or MSC 5.1), Atari TOS, OS9/68000, Coherent, VMS, and AmigaDos.
Ports to other operating systems are in progress; contact me before you
start porting it to some other OS, because somebody else may have
already done it for you.
Elvis is freely redistributable, in either source form or executable
form. There are no restrictions on how you may use it.
The file "elvisman.txt" contains the manual for elvis. It is a plain
ASCII file with nothing more exotic than a newline character. It is
formatted for 66-line, 80-column pages. There may also be an archive of
"*.ms" and "*.man" files, which contain the TROFF source text used to
generate that manual.
The file named "Makefile.mix" is used to compile elvis for all systems
except VMS and possibly MS-DOS. You should copy "Makefile.mix" to
"Makefile", and then edit "Makefile" to select the appropriate group of
settings for your system.
Author: Steve Kirkendall
14407 SW Teal Blvd. #C
Beaverton, OR 97005
E-mail: kirkenda@cs.pdx.edu
Phone: (503) 643-6980
****************************************************************************/
/* ctags.c */
/* This is a reimplementation of the ctags(1) program. It supports ANSI C,
* and has heaps o' flags. It is meant to be distributed with elvis.
*/
#include <stdio.h>
#include <string.h>
#include "config.h"
#ifndef FALSE
# define FALSE 0
# define TRUE 1
#endif
#ifndef TAGS
# define TAGS "tags"
#endif
#ifndef REFS
# define REFS "refs"
#endif
#ifndef BLKSIZE
# define BLKSIZE 1024
#endif
#include "ctype.c" /* yes, that really is the .c file, not the .h one. */
/* -------------------------------------------------------------------------- */
/* Some global variables */
/* The following boolean variables are set according to command line flags */
int incl_static; /* -s include static tags */
int incl_types; /* -t include typedefs and structs */
int incl_vars; /* -v include variables */
int make_refs; /* -r generate a "refs" file */
int append_files; /* -a append to "tags" [and "refs"] files */
/* The following are used for outputting to the "tags" and "refs" files */
FILE *tags; /* used for writing to the "tags" file */
FILE *refs; /* used for writing to the "refs" file */
/* -------------------------------------------------------------------------- */
/* These are used for reading a source file. It keeps track of line numbers */
char *file_name; /* name of the current file */
FILE *file_fp; /* stream used for reading the file */
long file_lnum; /* line number in the current file */
long file_seek; /* fseek() offset to the start of current line */
int file_afternl; /* boolean: was previous character a newline? */
int file_prevch; /* a single character that was ungotten */
int file_header; /* boolean: is the current file a header file? */
/* This function opens a file, and resets the line counter. If it fails, it
* it will display an error message and leave the file_fp set to NULL.
*/
void file_open(name)
char *name; /* name of file to be opened */
{
/* if another file was already open, then close it */
if (file_fp)
{
fclose(file_fp);
}
/* try to open the file for reading. The file must be opened in
* "binary" mode because otherwise fseek() would misbehave under DOS.
*/
#if MSDOS || TOS
file_fp = fopen(name, "rb");
#else
file_fp = fopen(name, "r");
#endif
if (!file_fp)
{
perror(name);
//- JAJ Added error output to stdout so Zeus will see it
printf("Error opening file '%s'\n", name);
}
/* reset the name & line number */
file_name = name;
file_lnum = 0L;
file_seek = 0L;
file_afternl = TRUE;
/* determine whether this is a header file */
file_header = FALSE;
name += strlen(name) - 2;
if (name >= file_name && name[0] == '.' && (name[1] == 'h' || name[1] == 'H'))
{
file_header = TRUE;
}
}
/* This function reads a single character from the stream. If the *previous*
* character was a newline, then it also increments file_lnum and sets
* file_offset.
*/
int file_getc()
{
int ch;
/* if there is an ungotten character, then return it. Don't do any
* other processing on it, though, because we already did that the
* first time it was read.
*/
if (file_prevch)
{
ch = file_prevch;
file_prevch = 0;
return ch;
}
/* if previous character was a newline, then we're starting a line */
if (file_afternl)
{
file_afternl = FALSE;
file_seek = ftell(file_fp);
file_lnum++;
}
/* Get a character. If no file is open, then return EOF */
ch = (file_fp ? getc(file_fp) : EOF);
/* if it is a newline, then remember that fact */
if (ch == '\n')
{
file_afternl = TRUE;
}
/* return the character */
return ch;
}
/* This function ungets a character from the current source file */
void file_ungetc(ch)
int ch; /* character to be ungotten */
{
file_prevch = ch;
}
/* This function copies the current line out some other fp. It has no effect
* on the file_getc() function. During copying, any '\' characters are doubled
* and a leading '^' or trailing '$' is also quoted. The newline character is
* not copied.
*
* This is meant to be used when generating a tag line.
*/
void file_copyline(seek, fp)
long seek; /* where the lines starts in the source file */
FILE *fp; /* the output stream to copy it to */
{
long oldseek;/* where the file's pointer was before we messed it up */
char ch; /* a single character from the file */
char next; /* the next character from this file */
/* go to the start of the line */
oldseek = ftell(file_fp);
fseek(file_fp, seek, 0);
/* if first character is '^', then emit \^ */
ch = getc(file_fp);
if (ch == '^')
{
putc('\\', fp);
putc('^', fp);
ch = getc(file_fp);
}
/* write everything up to, but not including, the newline */
while (ch != '\n')
{
/* preread the next character from this file */
next = getc(file_fp);
/* if character is '\', or a terminal '$', then quote it */
if (ch == '\\' || (ch == '$' && next == '\n'))
{
putc('\\', fp);
}
putc(ch, fp);
/* next character... */
ch = next;
}
/* seek back to the old position */
fseek(file_fp, oldseek, 0);
}
/* -------------------------------------------------------------------------- */
/* This section handles preprocessor directives. It strips out all of the
* directives, and may emit a tag for #define directives.
*/
int cpp_afternl; /* boolean: look for '#' character? */
int cpp_prevch; /* an ungotten character, if any */
int cpp_refsok; /* boolean: can we echo characters out to "refs"? */
/* This function opens the file & resets variables */
void cpp_open(name)
char *name; /* name of source file to be opened */
{
/* use the lower-level file_open function to open the file */
file_open(name);
/* reset variables */
cpp_afternl = TRUE;
cpp_refsok = TRUE;
}
/* This function copies a character from the source file to the "refs" file */
void cpp_echo(ch)
int ch; /* the character to copy */
{
static wasnl;
/* echo non-EOF chars, unless not making "ref", or echo turned off */
if (ch != EOF && make_refs && cpp_refsok && !file_header)
{
/* try to avoid blank lines */
if (ch == '\n')
{
if (wasnl)
{
return;
}
wasnl = TRUE;
}
else
{
wasnl = FALSE;
}
/* add the character */
putc(ch, refs);
}
}
/* This function returns the next character which isn't part of a directive */
int cpp_getc()
{
static
int ch; /* the next input character */
char *scan;
//- JAJ added
int count;
/* if we have an ungotten character, then return it */
if (cpp_prevch)
{
ch = cpp_prevch;
cpp_prevch = 0;
return ch;
}
/* Get a character from the file. Return it if not special '#' */
ch = file_getc();
if (ch == '\n')
{
cpp_afternl = TRUE;
cpp_echo(ch);
return ch;
}
else if (ch != '#' || !cpp_afternl)
{
/* normal character. Any non-whitespace should turn off afternl */
if (ch != ' ' && ch != '\t')
{
cpp_afternl = FALSE;
}
cpp_echo(ch);
return ch;
}
/* Yikes! We found a directive */
/* see whether this is a #define line */
scan = " define ";
while (*scan)
{
if (*scan == ' ')
{
/* space character matches any whitespace */
do
{
ch = file_getc();
} while (ch == ' ' || ch == '\t');
file_ungetc(ch);
}
else
{
/* other characters should match exactly */
ch = file_getc();
if (ch != *scan)
{
file_ungetc(ch);
break;
}
}
scan++;
}
/* is this a #define line? and should we generate a tag for it? */
if (!*scan && (file_header || incl_static))
{
/* if not a header, then this will be a static tag */
if (!file_header)
{
//- JAJ change the way the data is displayed
// fputs(file_name, tags);
// putc(':', tags);
fprintf(tags, "%s: ", "define ");
}
//- JAJ added
count = 0;
/* output the tag name */
for (ch = file_getc(); isalnum(ch) || ch == '_'; ch = file_getc())
{
putc(ch, tags);
//- JAJ keep track of the number of chars processed
count++;
}
//- JAJ change the way the data is displayed
if (count < 30)
for (ch = count; ch < 30; ++ch)
putc(' ', tags);
/* output a tab, the filename, another tab, and the line number */
//- JAJ change the way the data is displayed
// fprintf(tags, "\t%s\t%ld\n", file_name, file_lnum);
fprintf(tags, " %s\t%ld\n", file_name, file_lnum);
}
/* skip to the end of the directive -- a newline that isn't preceded
* by a '\' character.
*/
while (ch != EOF && ch != '\n')
{
if (ch == '\\')
{
ch = file_getc();
}
ch = file_getc();
}
/* return the newline that we found at the end of the directive */
cpp_echo(ch);
return ch;
}
/* This puts a character back into the input queue for the source file */
cpp_ungetc(ch)
int ch; /* a character to be ungotten */
{
cpp_prevch = ch;
}
/* -------------------------------------------------------------------------- */
/* This is the lexical analyser. It gets characters from the preprocessor,
* and gives tokens to the parser. Some special codes are...
* (deleted) /*...* / (comments)
* (deleted) //...\n (comments)
* (deleted) (* (parens used in complex declaration)
* (deleted) [...] (array subscript, when ... contains no ])
* (deleted) struct (intro to structure declaration)
* BODY {...} ('{' can occur anywhere, '}' only at BOW if ... has '{')
* ARGS (...{ (args of function, not extern or forward)
* ARGS (...); (args of an extern/forward function declaration)
* COMMA , (separate declarations that have same scope)
* SEMICOLON ; (separate declarations that have different scope)
* SEMICOLON =...; (initializer)
* TYPEDEF typedef (the "typedef" keyword)
* STATIC static (the "static" keyword)
* STATIC private (the "static" keyword)
* STATIC PRIVATE (the "static" keyword)
* NAME [a-z]+ (really any valid name that isn't reserved word)
*/
/* #define EOF -1 */
#define DELETED 0
#define BODY 1
#define ARGS 2
#define COMMA 3
#define SEMICOLON 4
#define TYPEDEF 5
#define STATIC 6
#define EXTERN 7
#define NAME 8
char lex_name[BLKSIZE]; /* the name of a "NAME" token */
long lex_seek; /* start of line that contains lex_name */
lex_gettoken()
{
int ch; /* a character from the preprocessor */
int next; /* the next character */
int token; /* the token that we'll return */
int i;
/* loop until we get a token that isn't "DELETED" */
do
{
/* get the next character */
ch = cpp_getc();
/* process the character */
switch (ch)
{
case ',':
token = COMMA;
break;
case ';':
token = SEMICOLON;
break;
case '/':
/* get the next character */
ch = cpp_getc();
switch (ch)
{
case '*': /* start of C comment */
ch = cpp_getc();
next = cpp_getc();
while (next != EOF && (ch != '*' || next != '/'))
{
ch = next;
next = cpp_getc();
}
break;
case '/': /* start of a C++ comment */
do
{
ch = cpp_getc();
} while (ch != '\n' && ch != EOF);
break;
default: /* some other slash */
cpp_ungetc(ch);
}
token = DELETED;
break;
case '(':
ch = cpp_getc();
if (ch == '*')
{
token = DELETED;
}
else
{
next = cpp_getc();
while (ch != '{' && ch != EOF && (ch != ')' || next != ';'))/*}*/
{
ch = next;
next = cpp_getc();
}
if (ch == '{')/*}*/
{
cpp_ungetc(ch);
}
else if (next == ';')
{
cpp_ungetc(next);
}
token = ARGS;
}
break;
case '{':/*}*/
/* don't send the next characters to "refs" */
cpp_refsok = FALSE;
/* skip ahead to closing '}', or to embedded '{' */
do
{
ch = cpp_getc();
} while (ch != '{' && ch != '}' && ch != EOF);
/* if has embedded '{', then skip to '}' in column 1 */
if (ch == '{') /*}*/
{
ch = cpp_getc();
next = cpp_getc();
while (ch != EOF && (ch != '\n' || next != '}'))/*{*/
{
ch = next;
next = cpp_getc();
}
}
/* resume "refs" processing */
cpp_refsok = TRUE;
cpp_echo('}');
token = BODY;
break;
case '[':
/* skip to matching ']' */
do
{
ch = cpp_getc();
} while (ch != ']' && ch != EOF);
token = DELETED;
break;
case '=':
/* skip to next ';' */
do
{
ch = cpp_getc();
/* leave array initializers out of "refs" */
if (ch == '{')
{
cpp_refsok = FALSE;
}
} while (ch != ';' && ch != EOF);
/* resume echoing to "refs" */
if (!cpp_refsok)
{
cpp_refsok = TRUE;
cpp_echo('}');
cpp_echo(';');
}
token = SEMICOLON;
break;
case EOF:
token = EOF;
break;
default:
/* is this the start of a name/keyword? */
if (isalpha(ch) || ch == '_')
{
/* collect the whole word */
lex_name[0] = ch;
for (i = 1, ch = cpp_getc();
i < BLKSIZE - 1 && (isalnum(ch) || ch == '_');
i++, ch = cpp_getc())
{
lex_name[i] = ch;
}
lex_name[i] = '\0';
cpp_ungetc(ch);
/* is it a reserved word? */
if (!strcmp(lex_name, "typedef"))
{
token = TYPEDEF;
lex_seek = -1L;
}
else if (!strcmp(lex_name, "static")
|| !strcmp(lex_name, "private")
|| !strcmp(lex_name, "PRIVATE"))
{
token = STATIC;
lex_seek = -1L;
}
else if (!strcmp(lex_name, "extern")
|| !strcmp(lex_name, "EXTERN")
|| !strcmp(lex_name, "FORWARD"))
{
token = EXTERN;
lex_seek = -1L;
}
else
{
token = NAME;
lex_seek = file_seek;
}
}
else /* not part of a name/keyword */
{
token = DELETED;
}
} /* end switch(ch) */
} while (token == DELETED);
return token;
}
/* -------------------------------------------------------------------------- */
/* This is the parser. It locates tag candidates, and then decides whether to
* generate a tag for them.
*/
/* This function generates a tag for the object in lex_name, whose tag line is
* located at a given seek offset.
*/
void maketag(scope, seek)
int scope; /* 0 if global, or STATIC if static */
long seek; /* the seek offset of the line */
{
/* output the tagname and filename fields */
if (scope == EXTERN)
{
/* whoa! we should *never* output a tag for "extern" decl */
return;
}
//- JAJ change the way the data is displayed
else if (scope == STATIC)
{
fprintf(tags, "%s: %-30.35s %s\t%ld\t", "static ", lex_name, file_name, file_lnum);
}
else if (scope == BODY)
{
fprintf(tags, "%s: %-30.35s %s\t%ld\t", "function", lex_name, file_name, file_lnum);
}
else
{
fprintf(tags, "%s: %-30.35s %s\t%ld\t", "global ", lex_name, file_name, file_lnum);
}
/* output the target line */
putc('/', tags);
putc('^', tags);
file_copyline(seek, tags);
putc('$', tags);
putc('/', tags);
putc('\n', tags);
}
/* This function parses a source file, adding any tags that it finds */
void ctags(name)
char *name; /* the name of a source file to be checked */
{
int prev; /* the previous token from the source file */
int token; /* the current token from the source file */
int scope; /* normally 0, but could be a TYPEDEF or STATIC token */
int gotname;/* boolean: does lex_name contain a tag candidate? */
long tagseek;/* start of line that contains lex_name */
/* open the file */
cpp_open(name);
/* reset */
scope = 0;
gotname = FALSE;
token = SEMICOLON;
/* parse until the end of the file */
while (prev = token, (token = lex_gettoken()) != EOF)
{
/* scope keyword? */
if (token == TYPEDEF || token == STATIC || token == EXTERN)
{
scope = token;
gotname = FALSE;
continue;
}
/* name of a possible tag candidate? */
if (token == NAME)
{
tagseek = file_seek;
gotname = TRUE;
continue;
}
/* if NAME BODY, without ARGS, then NAME is a struct tag */
if (gotname && token == BODY && prev != ARGS)
{
gotname = FALSE;
/* ignore if in typedef -- better name is coming soon */
if (scope == TYPEDEF)
{
continue;
}
/* generate a tag, if -t and maybe -s */
if (incl_types && (file_header || incl_static))
{
maketag(file_header ? 0 : STATIC, tagseek);
}
}
/* If NAME ARGS BODY, then NAME is a function */
if (gotname && prev == ARGS && token == BODY)
{
gotname = FALSE;
/* generate a tag, maybe checking -s */
if (scope != STATIC || incl_static)
{
//- JAJ changed line to recognize functions
//-- maketag(scope, tagseek);
maketag(BODY, tagseek);
}
}
/* If NAME SEMICOLON or NAME COMMA, then NAME is var/typedef */
if (gotname && (token == SEMICOLON || token == COMMA))
{
gotname = FALSE;
/* generate a tag, if -v/-t and maybe -s */
if (scope == TYPEDEF && incl_types && (file_header || incl_static)
|| scope == STATIC && incl_vars && incl_static
|| incl_vars)
{
/* a TYPEDEF outside of a header is STATIC */
if (scope == TYPEDEF && !file_header)
{
maketag(STATIC, tagseek);
}
else /* use whatever scope was declared */
{
maketag(scope, tagseek);
}
}
}
/* reset after a semicolon or ARGS BODY pair */
if (token == SEMICOLON || (prev == ARGS && token == BODY))
{
scope = 0;
gotname = FALSE;
}
}
/* The source file will be automatically closed */
}
/* -------------------------------------------------------------------------- */
void usage()
{
//- JAJ make the output go to standard out so Zeus could catch it
fprintf(stdout, "usage: ctags [flags] filenames...\n");
fprintf(stdout, "\t-s include static functions\n");
fprintf(stdout, "\t-t include typedefs\n");
fprintf(stdout, "\t-v include variable declarations\n");
fprintf(stdout, "\t-r generate a \"refs\" file, too\n");
fprintf(stdout, "\t-a append to \"tags\", instead of overwriting\n");
exit(2);
}
#if AMIGA
# include "amiwild.c"
#endif
#if VMS
# include "vmswild.c"
#endif
main(argc, argv)
int argc;
char **argv;
{
int i, j;
#if MSDOS || TOS
char **wildexpand();
argv = wildexpand(&argc, argv);
#endif
/* build the tables used by the ctype macros */
_ct_init("");
/* parse the option flags */
for (i = 1; i < argc && argv[i][0] == '-'; i++)
{
for (j = 1; argv[i][j]; j++)
{
switch (argv[i][j])
{
case 's': incl_static = TRUE; break;
case 't': incl_types = TRUE; break;
case 'v': incl_vars = TRUE; break;
case 'r': make_refs = TRUE; break;
case 'a': append_files = TRUE; break;
default: usage();
}
}
}
/* There should always be at least one source file named in args */
if (i == argc)
{
usage();
}
/* open the "tags" and maybe "refs" files */
//- JAJ make the output go to standard out so Zeus could catch it
// tags = fopen(TAGS, append_files ? "a" : "w");
tags = stdout;
if (!tags)
{
perror(TAGS);
exit(3);
}
if (make_refs)
{
refs = fopen(REFS, append_files ? "a" : "w");
if (!refs)
{
perror(REFS);
//- JAJ Added error output to stdout so Zeus will see it
printf("Error opening file '%s'\n", REFS);
exit(4);
}
}
/* parse each source file */
for (; i < argc; i++)
{
ctags(argv[i]);
}
/* close "tags" and maybe "refs" */
fclose(tags);
if (make_refs)
{
fclose(refs);
}
#ifdef SORT
/* This is a hack which will sort the tags list. It should
* on UNIX and OS-9. You may have trouble with csh. Note
* that the tags list only has to be sorted if you intend to
* use it with the real vi; elvis permits unsorted tags.
*/
# if OSK
system("qsort tags >-_tags; -nx; del tags; rename _tags tags");
# else
system("sort tags >_tags$$; mv _tags$$ tags");
# endif
#endif
exit(0);
/*NOTREACHED*/
}
#if MSDOS || TOS
# define WILDCARD_NO_MAIN
# include "wildcard.c"
#endif